﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Security.Cryptography.X509Certificates;

namespace NHINDirect
{
    public abstract class CertificateStore : IX509Store, ICertificateService, IEnumerable<X509Certificate2>
    {
        Predicate<X509Certificate2> m_criteria;
        
        protected CertificateStore()
        {
        }
        
        protected CertificateStore(Predicate<X509Certificate2> criteria)
        {
            this.m_criteria = criteria;
        }
        
        public Predicate<X509Certificate2> Criteria
        {
            get
            {
                return this.m_criteria;
            }
            set
            {
                this.m_criteria = value;
            }
        }

        public abstract X509Certificate2Collection this[string subjectName]
        {
            get;
        }
        public abstract bool Contains(X509Certificate2 cert);
        public abstract void Add(X509Certificate2 cert);
        public abstract void Remove(X509Certificate2 cert);

        public bool MatchesCriteria(X509Certificate2 cert)
        {
            if (cert == null)
            {
                return false;
            }

            return (this.m_criteria == null || this.m_criteria(cert));
        }

        protected void ValidateCriteria(X509Certificate2 cert)
        {
            if (!this.MatchesCriteria(cert))
            {
                throw new ArgumentException("Criteria mismatch");
            }
        }

        public void CopyFrom(X509Store source)
        {
            if (source == null)
            {
                throw new ArgumentNullException();
            }

            this.Update(source.Certificates.Enumerate(this.Criteria));
        }

        public void CopyFrom(X509Store source, Predicate<X509Certificate2> criteria)
        {
            if (source == null)
            {
                throw new ArgumentNullException();
            }

            this.Update(source.Certificates.Enumerate(criteria));
        }

        public void Add(IEnumerable<X509Certificate2> certs)
        {
            if (certs == null)
            {
                throw new ArgumentNullException();
            }
            foreach(X509Certificate2 cert in certs)
            {
                this.Add(cert);
            }
        }
        
        public void Add(X509Certificate2Collection certs)
        {
            if (certs == null)
            {
                throw new ArgumentNullException();
            }
            for (int i = 0, count = certs.Count; i < count; ++i)
            {
                this.Add(certs[i]);
            }        
        }

        public void ImportKeyFile(string filePath, string password, X509KeyStorageFlags flags)
        {
            X509Certificate2Collection certs = new X509Certificate2Collection();
            certs.Import(filePath, password, flags);
            this.Add(certs);
        }

        public void ExportKeyFile(string filePath, string password, X509ContentType type)
        {
            X509Certificate2Collection certs = new X509Certificate2Collection();
            certs.Add(this.GetCertificates());
            byte[] blob = certs.Export(type, password);
            
            System.IO.File.WriteAllBytes(filePath, blob);
        }
               
        public void Remove(IEnumerable<X509Certificate2> certs)
        {
            if (certs == null)
            {
                throw new ArgumentNullException();
            }
            foreach (X509Certificate2 cert in certs)
            {
                this.Remove(cert);
            }
        }

        public void Remove(X509Certificate2Collection certs)
        {
            if (certs == null)
            {
                throw new ArgumentNullException();
            }
            for (int i = 0, count = certs.Count; i < count; ++i)
            {
                this.Remove(certs[i]);
            }
        }
        
        public void Remove(string subjectName)
        {
            X509Certificate2Collection certs = this[subjectName];
            if (certs != null)
            {
                this.Remove(certs);
            }
        }
        
        public void Update(X509Certificate2 cert)
        {
            this.ValidateCriteria(cert);
            if (this.Contains(cert))
            {
                this.Remove(cert);
            }
            this.Add(cert);
        }

        public void Update(IEnumerable<X509Certificate2> certs)
        {
            if (certs == null)
            {
                throw new ArgumentNullException();
            }
            foreach (X509Certificate2 cert in certs)
            {
                this.Update(cert);
            }
        }

        public abstract IEnumerable<X509Certificate2> GetCertificates();

        public IEnumerator<X509Certificate2> GetEnumerator()
        {
            return this.GetCertificates().GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        //---------------------------------------------
        //
        // ICertificateService
        //
        //---------------------------------------------
        public X509Certificate2 GetPrivateCertificate(MailAddress address)
        {
            return this.GetFirstUsableCert(address);
        }

        public X509Certificate2 GetCertificate(MailAddress address)
        {
            return this.GetFirstUsableCert(address);
        }

        X509Certificate2 GetFirstUsableCert(MailAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException();
            }

            X509Certificate2Collection certs = this[address.Address];
            if (certs == null)
            {
                certs = this[address.Host];
            }
            if (certs == null)
            {
                return null;
            }

            return certs.FindUsable();
        }

        public CertificateIndex Index()
        {
            return new CertificateIndex(this);
        }

        public X509Certificate2 GetFirstUsable()
        {
            return CryptoExtensions.FindUsable(this);
        }

        public virtual void Dispose()
        {
        }
    }
}
